{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "ac47bfab-0f4f-42ce-8bb6-898ef22a0338",
   "metadata": {},
   "source": [
    "# How to merge consecutive messages of the same type\n",
    "\n",
    ":::note\n",
    "The `mergeMessageRuns` function is available in `@langchain/core` version `0.2.8` and above.\n",
    ":::\n",
    "\n",
    "Certain models do not support passing in consecutive messages of the same type (a.k.a. \"runs\" of the same message type).\n",
    "\n",
    "The `mergeMessageRuns` utility makes it easy to merge consecutive messages of the same type.\n",
    "\n",
    "## Basic usage"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "1a215bbb-c05c-40b0-a6fd-d94884d517df",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\n",
      "  \"role\": \"system\",\n",
      "  \"content\": \"you're a good assistant.\\nyou always respond with a joke.\"\n",
      "}\n",
      "\n",
      "{\n",
      "  \"role\": \"human\",\n",
      "  \"content\": [\n",
      "    {\n",
      "      \"type\": \"text\",\n",
      "      \"text\": \"i wonder why it's called langchain\"\n",
      "    },\n",
      "    {\n",
      "      \"type\": \"text\",\n",
      "      \"text\": \"and who is harrison chasing anyways\"\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "\n",
      "{\n",
      "  \"role\": \"ai\",\n",
      "  \"content\": \"Well, I guess they thought \\\"WordRope\\\" and \\\"SentenceString\\\" just didn't have the same ring to it!\\nWhy, he's probably chasing after the last cup of coffee in the office!\"\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "import { HumanMessage, SystemMessage, AIMessage, mergeMessageRuns } from \"@langchain/core/messages\";\n",
    "\n",
    "const messages = [\n",
    "    new SystemMessage(\"you're a good assistant.\"),\n",
    "    new SystemMessage(\"you always respond with a joke.\"),\n",
    "    new HumanMessage({ content: [{\"type\": \"text\", \"text\": \"i wonder why it's called langchain\"}] }),\n",
    "    new HumanMessage(\"and who is harrison chasing anyways\"),\n",
    "    new AIMessage(\n",
    "        'Well, I guess they thought \"WordRope\" and \"SentenceString\" just didn\\'t have the same ring to it!'\n",
    "    ),\n",
    "    new AIMessage(\"Why, he's probably chasing after the last cup of coffee in the office!\"),\n",
    "];\n",
    "\n",
    "const merged = mergeMessageRuns(messages);\n",
    "console.log(merged.map((x) => JSON.stringify({\n",
    "    role: x._getType(),\n",
    "    content: x.content,\n",
    "}, null, 2)).join(\"\\n\\n\"));"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0544c811-7112-4b76-8877-cc897407c738",
   "metadata": {},
   "source": [
    "Notice that if the contents of one of the messages to merge is a list of content blocks then the merged message will have a list of content blocks. And if both messages to merge have string contents then those are concatenated with a newline character."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1b2eee74-71c8-4168-b968-bca580c25d18",
   "metadata": {},
   "source": [
    "## Chaining\n",
    "\n",
    "`mergeMessageRuns` can be used in an imperatively (like above) or declaratively, making it easy to compose with other components in a chain:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "6d5a0283-11f8-435b-b27b-7b18f7693592",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "AIMessage {\n",
      "  lc_serializable: true,\n",
      "  lc_kwargs: {\n",
      "    content: [],\n",
      "    additional_kwargs: {\n",
      "      id: 'msg_01LsdS4bjQ3EznH7Tj4xujV1',\n",
      "      type: 'message',\n",
      "      role: 'assistant',\n",
      "      model: 'claude-3-sonnet-20240229',\n",
      "      stop_reason: 'end_turn',\n",
      "      stop_sequence: null,\n",
      "      usage: [Object]\n",
      "    },\n",
      "    tool_calls: [],\n",
      "    usage_metadata: { input_tokens: 84, output_tokens: 3, total_tokens: 87 },\n",
      "    invalid_tool_calls: [],\n",
      "    response_metadata: {}\n",
      "  },\n",
      "  lc_namespace: [ 'langchain_core', 'messages' ],\n",
      "  content: [],\n",
      "  name: undefined,\n",
      "  additional_kwargs: {\n",
      "    id: 'msg_01LsdS4bjQ3EznH7Tj4xujV1',\n",
      "    type: 'message',\n",
      "    role: 'assistant',\n",
      "    model: 'claude-3-sonnet-20240229',\n",
      "    stop_reason: 'end_turn',\n",
      "    stop_sequence: null,\n",
      "    usage: { input_tokens: 84, output_tokens: 3 }\n",
      "  },\n",
      "  response_metadata: {\n",
      "    id: 'msg_01LsdS4bjQ3EznH7Tj4xujV1',\n",
      "    model: 'claude-3-sonnet-20240229',\n",
      "    stop_reason: 'end_turn',\n",
      "    stop_sequence: null,\n",
      "    usage: { input_tokens: 84, output_tokens: 3 }\n",
      "  },\n",
      "  id: undefined,\n",
      "  tool_calls: [],\n",
      "  invalid_tool_calls: [],\n",
      "  usage_metadata: { input_tokens: 84, output_tokens: 3, total_tokens: 87 }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "import { ChatAnthropic } from \"@langchain/anthropic\";\n",
    "import { mergeMessageRuns } from \"@langchain/core/messages\";\n",
    "\n",
    "const llm = new ChatAnthropic({ model: \"claude-3-sonnet-20240229\", temperature: 0 });\n",
    "// Notice we don't pass in messages. This creates\n",
    "// a RunnableLambda that takes messages as input\n",
    "const merger = mergeMessageRuns();\n",
    "const chain = merger.pipe(llm);\n",
    "await chain.invoke(messages);"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "72e90dce-693c-4842-9526-ce6460fe956b",
   "metadata": {},
   "source": [
    "Looking at [the LangSmith trace](https://smith.langchain.com/public/48d256fb-fd7e-48a0-bdfd-217ab74ad01d/r) we can see that before the messages are passed to the model they are merged.\n",
    "\n",
    "Looking at just the merger, we can see that it's a Runnable object that can be invoked like all Runnables:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "460817a6-c327-429d-958e-181a8c46059c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[\n",
      "  SystemMessage {\n",
      "    lc_serializable: true,\n",
      "    lc_kwargs: {\n",
      "      content: \"you're a good assistant.\\nyou always respond with a joke.\",\n",
      "      name: undefined,\n",
      "      additional_kwargs: {},\n",
      "      response_metadata: {},\n",
      "      id: undefined\n",
      "    },\n",
      "    lc_namespace: [ 'langchain_core', 'messages' ],\n",
      "    content: \"you're a good assistant.\\nyou always respond with a joke.\",\n",
      "    name: undefined,\n",
      "    additional_kwargs: {},\n",
      "    response_metadata: {},\n",
      "    id: undefined\n",
      "  },\n",
      "  HumanMessage {\n",
      "    lc_serializable: true,\n",
      "    lc_kwargs: {\n",
      "      content: [Array],\n",
      "      name: undefined,\n",
      "      additional_kwargs: {},\n",
      "      response_metadata: {},\n",
      "      id: undefined\n",
      "    },\n",
      "    lc_namespace: [ 'langchain_core', 'messages' ],\n",
      "    content: [ [Object], [Object] ],\n",
      "    name: undefined,\n",
      "    additional_kwargs: {},\n",
      "    response_metadata: {},\n",
      "    id: undefined\n",
      "  },\n",
      "  AIMessage {\n",
      "    lc_serializable: true,\n",
      "    lc_kwargs: {\n",
      "      content: `Well, I guess they thought \"WordRope\" and \"SentenceString\" just didn't have the same ring to it!\\n` +\n",
      "        \"Why, he's probably chasing after the last cup of coffee in the office!\",\n",
      "      name: undefined,\n",
      "      additional_kwargs: {},\n",
      "      response_metadata: {},\n",
      "      id: undefined,\n",
      "      tool_calls: [],\n",
      "      invalid_tool_calls: [],\n",
      "      usage_metadata: undefined\n",
      "    },\n",
      "    lc_namespace: [ 'langchain_core', 'messages' ],\n",
      "    content: `Well, I guess they thought \"WordRope\" and \"SentenceString\" just didn't have the same ring to it!\\n` +\n",
      "      \"Why, he's probably chasing after the last cup of coffee in the office!\",\n",
      "    name: undefined,\n",
      "    additional_kwargs: {},\n",
      "    response_metadata: {},\n",
      "    id: undefined,\n",
      "    tool_calls: [],\n",
      "    invalid_tool_calls: [],\n",
      "    usage_metadata: undefined\n",
      "  }\n",
      "]\n"
     ]
    }
   ],
   "source": [
    "await merger.invoke(messages)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4548d916-ce21-4dc6-8f19-eedb8003ace6",
   "metadata": {},
   "source": [
    "## API reference\n",
    "\n",
    "For a complete description of all arguments head to the [API reference](https://api.js.langchain.com/functions/langchain_core.messages.mergeMessageRuns.html)."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "TypeScript",
   "language": "typescript",
   "name": "tslab"
  },
  "language_info": {
   "codemirror_mode": {
    "mode": "typescript",
    "name": "javascript",
    "typescript": true
   },
   "file_extension": ".ts",
   "mimetype": "text/typescript",
   "name": "typescript",
   "version": "3.7.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
